import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class FilterTracesIndependentForBaseMop {
    public static String mainClassName;
    /**通过trace的操作关键字判断一行trace是不是我们需要的trace
     * @param line 待判断的trace行
     * @return 如果是我们需要的trace返回true，不是则返回false
     */
    public static boolean isNeededDslTrace(String line) {
        String[] uselessTraceCommands = {"Del", "Add Class root", "Add Set root"};
        String lineCommand = RegularExpressionProcess(line, "\\[gc,heap\\s+\\] (.*?) (H|H1):");
        // 单独处理 NewM
        // [0.313s][info][gc,heap     ] NewM C:[[Ljava/lang/invoke/LambdaForm$Name;, D:5 10 , T:main, M:java/lang/invoke/LambdaForm.<clinit> ()V
        if (lineCommand == null) {
            // NewM的例子
            // NewM C:[[Ljava/lang/invoke/LambdaForm$Name;, D:5 10 , T:main, M:java/lang/invoke/LambdaForm.<clinit> ()V
            String newMClassName = RegularExpressionProcess(line, "\\[gc,heap\\s+\\] NewM C:(.*?),"); // 获得NewM中的Class名称
            if (newMClassName != null) {
                // 表示这行语句是NewM
                return true;
            }
        } else {
            // 过滤掉我们不需要的trace操作
            for (String uselessCommand : uselessTraceCommands) {
                if (lineCommand.contains(uselessCommand)) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 如果目标路径文件夹存在，则删除
     *
     * @param dstFolderPath 目标文件夹的路径
     */
    public static void deleteExsitDstFolder(String dstFolderPath) {
        // 创建File对象
        File directory = new File(dstFolderPath);
        if (directory.exists()) {
            File[] files = directory.listFiles();
            if (files != null) {
                for (File file : files) {
                    // 删除文件
                    file.delete();
                }
            }
            // 删除空目录
            directory.delete();
        }
    }
    /**
     * 正则表达式处理函数，根据pattern提取出input中第一个满足要求的字符串
     *
     * @param input   待匹配字符串
     * @param pattern 匹配模板
     * @return 满足要求的字符串
     */
    public static String RegularExpressionProcess(String input, String pattern) {
        // 创建一个 Pattern 对象
        Pattern compiledPattern = Pattern.compile(pattern);
        // 使用 Pattern 对象创建一个 Matcher 对象
        Matcher matcher = compiledPattern.matcher(input);
        // 查找第一个匹配的子序列
        if (matcher.find()) {
            String matchStr = matcher.group(1);
            return matchStr;
        }
        return null;
    }


    /**
     * 正则表达式处理函数，判断input中能不能找到pattern模式的字符串
     *
     * @param input   待匹配字符串
     * @param pattern 匹配模板
     * @return true表示input中能找到满足pattern的字符串
     */
    public static boolean isInputContainsPattern(String input, String pattern) {
        // 创建一个 Pattern 对象
        Pattern compiledPattern = Pattern.compile(pattern);
        // 使用 Pattern 对象创建一个 Matcher 对象
        Matcher matcher = compiledPattern.matcher(input);
        // 查找第一个匹配的子序列
        if (matcher.find()) {
            return true;
        }
        return false;
    }

    /**
     * append一行字符串到目标文件
     *
     * @param line        字符串内容
     * @param dstFilePath 目标文件所在路径
     */
    public static void writeLineToDstFile(String line, String dstFilePath) {
        // 创建File对象
        File file = new File(dstFilePath);
        // 获取文件的父目录
        File parentDirectory = file.getParentFile();
        // 如果父目录不存在，则创建父目录
        if (!parentDirectory.exists()) {
            parentDirectory.mkdirs();
        }
        // 将line写入目标文件路径
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(dstFilePath, true))) {
            writer.write(line + "\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 判断一个threadName是不是需要的（JVM内部会启动一些thread，这些thread不是我们需要的），如果是返回true，不是返回false
     *
     * @param threadName 待判断的线程名称
     * @return 如果是需要的线程名，返回true
     */
    public static boolean isNeededThreadName(String threadName) {
        /**不需要的线程名列表
         * null
         * Reference Handler
         * Unknown thread
         * Common-Cleaner
         * DestroyJavaVM
         * Signal Dispatcher
         * Finalizer
         * <no-name - thread is attaching>
         */
        String[] uselessThreadNames = {"null", "Reference Handler", "Unknown thread", "Common-Cleaner", "DestroyJavaVM", "Signal Dispatcher",
                "Finalizer", "<no-name - thread is attaching>"};
        for (String uselessThreadName : uselessThreadNames) {
            if (uselessThreadName.equals(threadName)) {
                return false;
            }
        }
        return true;
    }


    /**
     * 初始的trace序列包含GC日志和我们定义的DSL语句，该函数主要有以下功能
     * 1. 清洗trace，去除GC日志部分
     * 2. 删除我们不需要的自定义trace语句，具体详见isNeededDslTrace函数
     * 3. 保留我们想要线程的trace，有些线程是JVM内部的，这些线程不是我们想要的，不想要的线程名参见isNeededThreadName函数
     * 4. 将大的trace按线程名分为小的trace，特别对于main线程而言，保留应用程序main函数的trace内容
     *
     * @param srcFilePath   源trace文件路径
     * @param dstFolderPath 处理后的trace文件夹的路径
     * @throws FileNotFoundException
     */
    public static void filterTraceRecords(String srcFilePath, String dstFolderPath, int num) throws FileNotFoundException {
//        // 如果dstFolderPath存在，则删除
//        deleteExsitDstFolder(dstFolderPath);
        // 用一个set保留应用程序运行的线程名称
        Set<String> appThreadNamesSet = new HashSet<>();
        Scanner scanner = new Scanner(new FileReader(srcFilePath));
        boolean isMain = false; // main线程使用，表示当前的trace是不是在应用程序main函数的范围内
        boolean isStatic = false;//静态块检测
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            // 处理主要是区分GC trace还是我们自定义DSL trace
            if (isInputContainsPattern(line, "\\[gc,heap\\s+\\] (New|NewM|NewL|PutA|Put|Add|Del|Rep|Return|Get|GetA)")) {
                // 是我们自定义的trace，判断是不是我们需要的trace
                if (isNeededDslTrace(line)) {
                    // 获得这一行trace所在线程名称
                    String threadName = RegularExpressionProcess(line, "T:(.*?)(,|$)");

                    // 如果是需要的线程名
                    if (isNeededThreadName(threadName)) {
                        appThreadNamesSet.add(threadName);
                        if ("main".equals(threadName)) {
                            String dstFilePath = dstFolderPath + "rawRefinedTraces_" + num + ".txt" ; // 这行trace待写入的文件路径
                            // 如果是main线程，则要截取应用程序的main函数
                            // 判断line是不是main函数开始
                            // main函数开始trace例子 [26.323s][info][gc,heap     ] Add PR H:0x00000000266713d4, C:[Ljava/lang/String;, T:main, MIS:true, M:Test.main ([Ljava/lang/String;)V
                            if (isInputContainsPattern(line, "Add PR H:.*?, C:\\[Ljava/lang/String;, T:main, MIS:true, M:"+mainClassName+"\\.main \\(\\[Ljava/lang/String;\\)V")) {
                                // 如果这行内容是main函数开始的trace
                                isMain = true;
                            }
                            // 判断line是不是main函数的结束
                            // main函数结束trace例子 [26.348s][info][gc,heap     ] Add PR H:0x0000000050040f0c, C:java/lang/Thread, T:main, MIS:false, M:java/lang/Thread.exit ()V
                            if (isInputContainsPattern(line, "Add PR H:.*?, C:java/lang/Thread, T:main, MIS:false, M:java/lang/Thread\\.exit \\(\\)V")) {
                                isMain = false;
                            }
                            if (isMain) {
                                // 如果是main函数范围的trace，才进行处理
//                                writeDslTraceToFile(line, dstFilePath);
                                writeLineToDstFile(line, dstFilePath);
                            }
                            //判断line是不是clinit范围
                            if (isInputContainsPattern(line, "Add PR H:.*?, C:.*?, T:main, M:"+mainClassName+"\\.<clinit> \\(\\)V")) {
                                isStatic = true;
                            } else if (isInputContainsPattern(line, "Return H:.*?, C:.*?, T:main, M:"+mainClassName+"\\.<clinit> \\(\\)V")) {
                                isStatic = false;
                                writeLineToDstFile(line, dstFolderPath + "staticClinitTracesOfMainClass.txt");
                            }
                            if (isStatic) writeLineToDstFile(line, dstFolderPath + "staticClinitTracesOfMainClass.txt");
                        } else {
                            String dstFilePath = dstFolderPath + threadName + "_temp.txt"; // 这行trace待写入的文件路径
                            // 其他线程直接输出线程的trace到对应文件
//                            writeDslTraceToFile(line, dstFilePath);
                            writeLineToDstFile(line, dstFilePath);
                        }
                    }
                }

            }

        }
//        // 输出应用程序所有的线程名
//        for (String appThreadName : appThreadNamesSet) {
//            System.out.println(appThreadName);
//        }
        // 保存每个非main thread的信息，按顺序为 传入的Thread对象id, 线程名称, run方法MethodName, 线程类名
        // 例如 Add PR H:0x0000000076462bc3, C:MyThread, T:Thread-0, M:MyThread.run ()V
        List<List<String>> threadInfoLists = new ArrayList<>();

        // 对每个非main线程的trace再进行处理，去除掉run方法之外的Traces，将处理后的traces保存在 threadName.txt 文件中
        // 并在处理的过程中记录Thread的信息保存到threadInfoLists中
        for (String threadName : appThreadNamesSet) {
            if (!"main".equals(threadName)) {
                filterOneThreadTraces(dstFolderPath + threadName + "_temp.txt",
                        dstFolderPath + threadName + ".txt", threadName, threadInfoLists);
            }
        }
        // 将每个非main线程的线程信息输出到文件 ThreadsInfo.txt中
        String threadInfoFilePath = dstFolderPath + "ThreadsInfo.txt";
        writeThreadsInfoToDslFile(threadInfoLists, threadInfoFilePath);

    }

    /**
     * 将非main线程的线程信息输出到目标文件中
     *
     * @param threadInfoLists    非main线程的线程信息，包括 传入的Thread对象id, 线程名称, run方法MethodName, 线程类名
     * @param threadInfoFilePath 目标文件路径
     */
    public static void writeThreadsInfoToDslFile(List<List<String>> threadInfoLists, String threadInfoFilePath) {
        for (List<String> threadInfoListTemp : threadInfoLists) {
            // 将threadInfoListTemp里的string拼接成一行内容
            StringBuilder threadInfoStringBuilder = new StringBuilder();
            for (String infoStr : threadInfoListTemp) {
                threadInfoStringBuilder.append(infoStr + " | ");
            }
            writeLineToDstFile(threadInfoStringBuilder.toString(), threadInfoFilePath);
        }
    }

    /**
     * 由filterTraceRecords函数处理raw traces后生成的某个线程（非main线程）的traces里面除了run方法，在run方法结束的位置后还有其他内容
     * 1. 该方法想要去掉run方法结束后的其他内容
     * 2. 保存每个非main线程的信息，按顺序为 传入的Thread对象id, 线程名称, run方法MethodName, 线程类名
     *
     * @param srcFilePath     线程的源trace文件路径，例如Thread-0_temp.txt
     * @param dstFilePath     过滤了run方法结束位置后的内容，得到的traces所在的文件路径，例如Thread-0.txt
     * @param threadName      处理的线程名，例如Thread-0
     * @param threadInfoLists 保存每个非main thread的信息，按顺序为 传入的Thread对象id, 线程名称, run方法MethodName, 线程类名
     */
    public static void filterOneThreadTraces(String srcFilePath, String dstFilePath, String threadName, List<List<String>> threadInfoLists) throws FileNotFoundException {
        // 判断dstFilePath文件是否存在，如果存在，则删除
        File file = new File(dstFilePath);
        if (file.exists()) {
            file.delete();
        }

        Scanner scanner = new Scanner(new FileReader(srcFilePath));
        boolean isRunMethod = false; // 表示当前的trace是不是在run函数的范围内
        boolean isFirstLine = true; // 当前处理的trace是不是第一行
        String threadClassName = null; // 获得当前线程的类名
        String runMethodName = null; // 获得当前线程运行的run方法的名称
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            // 处理主要是区分GC trace还是我们自定义DSL trace
            if (isInputContainsPattern(line, "\\[gc,heap\\s+\\] (New|NewM|NewL|PutA|Put|Add|Del|Rep|Return|Get|GetA)")) {
                // 是我们自定义的trace，判断是不是我们需要的trace
                if (isNeededDslTrace(line)) {
                    if (isFirstLine) {
                        // 如果是第一行，获得线程的类名和run方法的名称
                        threadClassName = RegularExpressionProcess(line, "C:(.*?),");
                        runMethodName = RegularExpressionProcess(line, "M:(.*?)$");
                        // 保存该线程的信息
                        List<String> threadInfoList = new ArrayList<>();
                        // 获得传入的Thread对象id
                        String threadObjId = RegularExpressionProcess(line, "H:(.*?),");
                        // 将thread相关信息按顺序加入到threadInfoList中（顺序为传入的Thread对象id, 线程名称, run方法MethodName, 线程类名）
                        threadInfoList.add(threadObjId);
                        threadInfoList.add(threadName);
                        threadInfoList.add(runMethodName);
                        threadInfoList.add(threadClassName);
                        // 将当前线程信息保存到threadInfoLists
                        threadInfoLists.add(threadInfoList);
                        isFirstLine = false;
                    }

                    // 要截取线程的run函数
                    // 判断line是不是run函数开始
                    // run函数开始的例子 [26.908s][info][gc,heap     ] Add PR H:0x0000000065988f4f, C:MyThread, T:Thread-0, MIS:false, M:MyThread.run ()V
                    if (isInputContainsPattern(line, "Add PR H:.*?, C:.*?, T:" + threadName + ", MIS:false, M:.*?\\.run \\(\\)V")) {
                        // 如果这行内容是run函数开始的trace
                        isRunMethod = true;
                    }
                    // 判断line是不是run函数的结束
                    // [26.942s][info][gc,heap     ] Add PR H:0x0000000065988f4f, C:MyThread, T:Thread-0, MIS:false, M:java/lang/Thread.exit ()V
                    if (isInputContainsPattern(line, "Add PR H:.*?, C:.*?, T:" + threadName + ", MIS:false, M:java/lang/Thread\\.exit \\(\\)V")) {
                        isRunMethod = false;
                    }
                    if (isRunMethod) {
                        // 如果是run函数范围的trace，才进行处理
                        writeLineToDstFile(line, dstFilePath);
                    }
                }

            }

        }
        scanner.close();
        // 删除srcFilePath
        File srcFile = new File(srcFilePath);
        if (srcFile.exists()) {
            srcFile.delete();
        }
    }

    public static void main(String[] args) throws FileNotFoundException {
        // 注意: 运行过滤程序前需要配置一些参数
        // 参数1 运行的Java程序main函数所在的主类名
        mainClassName = "Test";
        // 源trace文件所在的路径
        String srcFilePath = args[0];
        // trace清洗后的内容所在的文件夹路径
        String dstFolderPath = args[1];
        // 处理的Java源文件序号
        int num = Integer.parseInt(args[2]);

        long startTime = System.currentTimeMillis();
        filterTraceRecords(srcFilePath, dstFolderPath, num);
        long endTime = System.currentTimeMillis();
        long executionTime = endTime - startTime;
        System.out.println("用时: " + executionTime / 1000.0 / 3600.0 + "小时");
    }
}
